<?php
defined('SYS_RUN') or exit();

class Route {

    const DEFAULT_ACTION = 'index';

    private $settings;

    private $paramTypes = array(
        'int'       => '([^\/0-9]+){1}',
        'string'    => '((.|^\/)+){1}'
    );

    private $q;

    function __construct() {
        $this->settings = Settings::get('routes');
        $this->q = Lib::get('request')->get('q');
    }

    public function to() {

        foreach($this->settings as $route => $action) {
            $aAction = $this-> _prepareAction($action);
            if($route == $this->q) {
                $this->_runController($aAction);
                return true;
            } else {
                $routeData = $this->_getRouteData($route);
                if(!$routeData) continue;
                if(preg_match($routeData['pattern'], $this->q)) {
                    $params = $this->_getParamsByPositions($routeData['paramPositions'], $this->q);
                    $this->_replaceActionParams($params ,$aAction);
                    $this->_runController($aAction, $params);
                    return true;
                }
            }
        }

        $defaultAction = self::DEFAULT_ACTION;
        $aAction = $this-> _prepareAction($defaultAction);
        $this->_runController($aAction);
    }

    private function _runController($aAction, $params = null) {
        try{
            $class = Controller::get($aAction[0]);
            if(!method_exists($class, $aAction[1])) {
                throw new RouteException();
            }
            if(is_null($params))
                $class->$aAction[1]();
            else  $class->$aAction[1]($params);
        } catch(RouteException $e) {
            throw new RouteException();
        }
    }

    private function _prepareAction($action) {
        $aAction = $this->_parseAction($action);
        $aAction[1] = $this->_getActionName($aAction[1]);
        return $aAction;
    }

    private function _parseAction($action) {
        $aAction = explode('/', $action);
        array_clear($aAction);

        if(!isset($aAction[1])) $aAction[1] = self::DEFAULT_ACTION;
        return $aAction;
    }

    private function _getActionName($action) {
        return 'action'.ucfirst($action);
    }

    private function _getRouteData($route) {
        $paths = explode('/', $route);
        array_clear($paths);
        if(!count($paths)) return false;

        $positions = array();
        $pattern = '/';
        $i = 0;
        foreach($paths as $elem) {
            $paramReg = $this->_getParamPattern($elem);
            if($paramReg) {
                $pattern .= $paramReg.'\/';
                $positions[] = $i;
            } else
                $pattern .= $elem.'\/';
            if($elem == end($paths)) $pattern .= '*$';
            $i++;
        }
        return array(
            'pattern'           => $pattern.'/',
            'paramPositions'    => $positions
        );
    }

    private function _getParamPattern($elem) {
        foreach($this->paramTypes as $typeName => $typeReg) {
            $reg = '/\{'.$typeName.'\}/';
            if(preg_match($reg, $elem))
                return $typeReg;
        }
        return false;
    }

    private function _getParamsByPositions($positions, $url) {
        $elems = array();
        $paths = explode('/', $url);
        array_clear($paths);
        foreach($positions as $pos) {
            $elems = array_merge($elems,
                    array_slice($paths, $pos, 1)
            );
        }

        if(count($elems) == 1)
            return $elems[0];
        else return $elems;
    }

    private function _replaceActionParams($params, &$aAction) {
        if(is_array($params)) {
            if(!isset($params[0])) return true;
            $paramValue = $params[0];
        }
        else $paramValue = $params;
        foreach($this->paramTypes as $keyType => $type) {
            $keyType = '{'.$keyType.'}';
            foreach($aAction as &$action)
                $action = str_replace($keyType, ucfirst($paramValue), $action);
        }
    }
}